// Copyright (c) 2025 Shenzhen Kaihong Digital Industry Development Co., Ltd.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::{path::PathBuf, str::FromStr};

use log::error;
use petgraph::graph::NodeIndex;

use crate::{database::{local::{find_installed_pkgs, pkg_stat_path, PKGS_ROOT}, pkg_stat::{PkgStatFile, PkgStatFileError, StatSpec}}, graph::DepKind};

use super::stat_dep_graph::DepGraph;

#[derive(Default)]
pub struct PkgStatResolver {
    pub dep_graph: DepGraph,
    unresolved: Vec<NodeIndex>,
}

#[derive(Debug, thiserror::Error)]
pub enum ResolveError {
    #[error("pkg '{0}' is not installed")]
    PkgNotInstalled(String),
    #[error("failed to read pkg stat file '{1}'")]
    PkgStatReadError(#[source] PkgStatFileError, PathBuf),
    #[error("failed to read directory '{1}'")]
    DirReadError(#[source] std::io::Error, PathBuf),
}

impl PkgStatResolver {
    pub async fn build_graph<'a, I>(&mut self, specs: I) -> Result<(), ResolveError>
    where I: Iterator<Item = &'a str>
    {
        self.preload_specs(specs)?;

        self.add_installed_nodes().await
            .map_err(|e| ResolveError::DirReadError(e, PKGS_ROOT.clone()))?;

        while let Some(id) = self.unresolved.pop() {
            self.add_node_depends(id).await;
        }

        Ok(())
    }

    fn preload_specs<'a, I>(&mut self, specs: I) -> Result<(), ResolveError>
    where I: Iterator<Item = &'a str> {
        for spec_str in specs {
            let stat_path = pkg_stat_path(spec_str);
            if !stat_path.exists() {
                return Err(ResolveError::PkgNotInstalled(spec_str.to_string()));
            }

            let id = self.dep_graph.add_node(spec_str.to_string());
            if id.index() + 1 == self.dep_graph.node_count() {
                self.unresolved.push(id);
            }
        }
        self.dep_graph.specs_range = 0..self.dep_graph.node_count();
        Ok(())
    }

    async fn add_installed_nodes(&mut self) -> Result<(), std::io::Error> {
        let mut find_stat = find_installed_pkgs().await?;
        while let Some((file_name, stat_spec)) = find_stat.next().await? {
            if stat_spec.impure {
                continue;
            }

            let id = self.dep_graph.add_node(file_name.strip_suffix(".stat").unwrap().to_string());
            if id.index() + 1 == self.dep_graph.node_count() {
                self.unresolved.push(id);
            }
        }
        Ok(())
    }

    async fn add_node_depends(&mut self, id: NodeIndex) {
        let spec_str = self.dep_graph.graph.node_weight(id).unwrap();
        let stat_path = pkg_stat_path(spec_str);

        if !stat_path.exists() {
            return;
        }

        let mut stat_file = match PkgStatFile::open(&stat_path).await {
            Ok(file) => file,
            Err(PkgStatFileError::Io(e)) => {
                error!("failed to read pkg stat file '{}': {}", stat_path.display(), e);
                return;
            },
            Err(e) => {
                error!("stat file '{}' is corrupted: {}", stat_path.display(), e);
                return;
            }
        };

        let (depends, host_depends, runtime_depends) = match self.read_pkg_stat_depends(&mut stat_file).await {
            Ok(res) => res,
            Err(e) => {
                error!("stat file '{}' is corrupted: {}", stat_path.display(), e);
                return;
            },
        };

        for dep in depends {
            self.add_node_depend(id, dep, DepKind::Build);
        }
        for dep in host_depends {
            self.add_node_depend(id, dep, DepKind::HostBuild);
        }
        for dep in runtime_depends {
            self.add_node_depend(id, dep, DepKind::Runtime);
        }
    }

    fn add_node_depend(
        &mut self,
        id: NodeIndex,
        dep: String,
        kind: DepKind,
    ) {
        if let Err(err) = StatSpec::from_str(&dep) {
            error!("invalid dependency '{}' in '{}': {}", dep, self.dep_graph.graph.node_weight(id).unwrap(), err);
            return;
        }
        
        let dep_id = self.dep_graph.add_node(dep);
        self.dep_graph.graph.add_edge(dep_id, id, kind);
        if dep_id.index() + 1 == self.dep_graph.node_count() {
            self.unresolved.push(dep_id);
        }
    }

    async fn read_pkg_stat_depends(&self, stat_file: &mut PkgStatFile) -> Result<(Vec<String>, Vec<String>, Vec<String>), PkgStatFileError> {
        let depends = stat_file.read_depends().await?;
        let host_depends = stat_file.read_host_depends().await?;
        let runtime_depends = stat_file.read_runtime_depends().await?;
        Ok((depends, host_depends, runtime_depends))
    }
}
